package com.example.redissiontest.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.TreeMap;

@Aspect
@Component
@Slf4j
public class DistributedLockAspect {

    public DistributedLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    private final RedissonClient redissonClient;

    @Pointcut("@annotation(com.example.redissiontest.config.DistributedLock)")
    public void distributedLockAspect() {}

    @Around(value = "distributedLockAspect()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        return doLock(pjp);
    }

    @After(value = "distributedLockAspect()")
    public void after() {
        log.info("after");
    }

    private Object doLock(ProceedingJoinPoint pjp) throws Throwable {
        //切点所在的类
        Class targetClass = pjp.getTarget().getClass();
        //使用了注解的方法
        String methodName = pjp.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature)pjp.getSignature()).getMethod().getParameterTypes();
        Method method = targetClass.getMethod(methodName, parameterTypes);
        Object[] arguments = pjp.getArgs();
        // 根据方法反射获取想要的锁名字
        String lockName = getLockName(method, arguments);
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        // 生成锁
        log.info("开始加锁");
        RLock lock = lock(lockName, distributedLock);
        try {
            return pjp.proceed();
        } finally {
            log.info("开始释放锁");
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private RLock lock(String lockName, DistributedLock distributedLock) throws InterruptedException {
        RLock lock = redissonClient.getLock(lockName);
        // 上锁
        if (distributedLock.tryLock()) {
            lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.timeUnit());
        } else {
            long leaseTime = distributedLock.leaseTime();
            if (leaseTime > 0) {
                lock.lock(distributedLock.leaseTime(), distributedLock.timeUnit());
            } else {
                lock.lock();
            }
        }
        return lock;
    }

    private String getLockName(Method method, Object[] arguments) {
        // 获取注解
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        // 获取前缀
        StringBuilder lockName = new StringBuilder(distributedLock.value());
        // 用来存储锁粒度标记
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        // 遍历参数找到粒度标记
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            // parameterAnnotations[i]：第i个参数的注解数组
            for (Annotation annotation : parameterAnnotations[i]) {
                // 遍历需要的注解（如果只有一个就是DistributedLockParam，如果一个参数上有多个会自动组合成DistributedLockParams）
                if (annotation instanceof DistributedLockParam) {
                    // 获取注解
                    DistributedLockParam distributedLockParam = (DistributedLockParam) annotation;
                    // 把属性放进treemap
                    fillTreeMap(distributedLockParam, arguments[i], treeMap);
                }else if (annotation instanceof DistributedLockParams) {
                    // 获取注解
                    DistributedLockParams distributedLockParams = (DistributedLockParams) annotation;
                    // 把属性放进treemap
                    for (DistributedLockParam distributedLockParam : distributedLockParams.value()) {
                        fillTreeMap(distributedLockParam, arguments[i], treeMap);
                    }
                    break;
                }
            }
        }
        // 收集完毕，拼接lockName
        separate(lockName, treeMap);
        return lockName.toString();
    }

    private void fillTreeMap(DistributedLockParam distributedLockParam, Object argument, TreeMap<Integer, String> treeMap) {
        // 获取属性名
        String field = distributedLockParam.value();
        if (field.equals("")) {
            // 基本属性直接用
            field = argument.toString();
        } else {
            // 对象反射拿数据
            try {
                String[] values = field.split("\\.");
                for (int i = 0; i < values.length; i++) {
                    Field declaredField = argument.getClass().getDeclaredField(values[i]);
                    declaredField.setAccessible(true);
                    if (i == values.length - 1) {
                        // 最后一个为真实对象，此时从中提取属性
                        field = declaredField.get(argument).toString();
                        // 如果这里不跳出，下一句执行会报错
                        break;
                    }
                    // 切换到下级对象
                    argument = declaredField.get(argument);
                }

            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("分布式锁参数有误");
            }
        }
        // 确定好以后放进treeMap中，自动排序
        treeMap.put(distributedLockParam.sort(), field);
    }

    private void separate(StringBuilder lockName, TreeMap<Integer, String> treeMap) {
        treeMap.values().forEach(s -> lockName.append(":").append(s));
    }

    @AfterThrowing(value = "distributedLockAspect()", throwing="ex")
    public void afterThrowing(Throwable ex) {
        log.info("after throw");
        throw new RuntimeException(ex);
    }

}